为什么我用 Lisp 编程
Lisp 从来都不是最受欢迎的编程语言。其他通用语言比它更受欢迎,而且如果丘奇和图灵的理论是正确的,那么它们最终能够完成 Lisp 能够完成的一切。它们拥有比 Lisp 更多的库和更大的用户群体,也更有可能被安装在计算机上。
然而,我更喜欢用 Lisp 编程。我总是开着一个 Lisp 交互式编程环境(REPL),并且用 Lisp 来编写原型和探索性代码。我为什么要这么做呢?Lisp 更容易记住,限制更少,不需要经过太多复杂的步骤,我的想法和程序之间的“摩擦”更小,它很容易定制,而且,坦白说,更有趣。
Lisp 令人畏惧的剑桥波兰表示法是统一且普遍的。我不需要记住某个表达式是用大括号还是方括号,也不需要记住操作符的优先级,或者那些毫无理由发明出来的奇怪标点符号语法。它对一切表达式都是(操作符 操作数……)。没有什么需要记住的。我基本上在 40 年前就不再注意那些括号了。我可以按照我喜欢的方式进行缩进。
我主要以函数式的方式编程,而 Lisp 有三个特性在这方面帮助极大。首先,如果你避免了副作用,它直接支持替换模型。你可以告诉 Lisp,当它看到这个简单的表达式时,可以直接用那个更复杂的表达式替换掉它。Lisp 不会总是迫使你用命令式的方式去思考。其次,由于语法是统一的,不依赖于上下文,你可以随意重构和移动代码。只要在平衡的括号内移动代码,你基本上就没问题。
第三,在大多数计算机语言中,你可以通过将特定值替换为命名该值的变量来抽象它。但是,你可以通过将命名量的变量替换为计算量的函数来进行进一步的抽象。在函数式编程中,你常常会淡化值和产生该值的函数之间的区别。毕竟,区别只在于等待答案的时间。在 Lisp 中,你只需要在表达式周围加上一个lambda
,就可以将表示对象的表达式变成计算对象的抽象。如今这已经不算什么大事了,但直到最近,只有 Lisp 拥有真正可用的lambda
表达式。即便如此,其他语言中的lambda
表达式通常都很笨拙。
函数式编程专注于函数(这不足为奇!)。函数是理想的黑箱抽象:输入值,输出答案。里面发生了什么?谁知道!谁在乎!但你可以把一些小的简单函数组合起来,得到更大更复杂的函数。这样做没有任何限制。如果你能把问题表述为“我有这个,我想要那个”,那么你就可以用函数式编程来解决它。的确,函数式编程需要一点练习才能习惯,但它允许你用非常简单的部件构建复杂的系统。一旦你掌握了它,你就会把一切都看作函数。(这不是限制。丘奇的 λ 演算是一种基于函数组合的计算模型。)
Lisp 让我能够以我产生新想法的速度去尝试它们。新程序与语言内置的程序无法区分,因此我可以像使用内置程序一样轻松地在它们之上构建。Lisp 的调试器意味着每次出现问题时,我不必停下来并从头开始重新启动。Lisp 的安全内存模型意味着在探索问题时,错误不会破坏我的工作空间。
Lisp 的 REPL 会评估表达式,这是 Lisp 程序的基本组成部分。你可以输入 Lisp 程序的一部分,并立即看到它的作用。如果它有效,你可以简单地将这个表达式嵌入到更大的程序中。你的程序会在你探索问题的过程中实时成型。
lisp 的动态类型为你提供了近乎自动的 ad hoc 多态性。如果你编写了一个调用+
的程序,它将适用于任何一对定义了+
操作符的对象。当然,如果你对类型很随意(例如,在字符串和数字的组合上定义+
),或者如果你避免自动类型强制转换,那么你可以编写非常通用的代码,它适用于你数据类型的超集。(动态类型是一把双刃剑。它允许快速原型开发,但它可能会隐藏在静态类型语言中会在编译时捕获的错误。)
其他语言可能有一些这样的特性,但 Lisp 把它们全部结合在一起。它是一种被设计为用于思考问题的工具的语言,而这就是编程的乐趣所在。